﻿// -----------------------------------------------------------------------
// <copyright file="StringGenerator.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentSerializer.Generators
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Text;
    using FluentSerializer.Utilities;

    /// <summary>
    /// 
    /// </summary>
    public static class StringGenerator
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="serializer"></param>
        /// <param name="parametersType"></param>
        /// <param name="type"></param>
        /// <param name="helper"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "More readable this way")]
        public static Descriptor WithByteLengthBuildSerializer(Serializer serializer, Type parametersType, Type type, Type helper, Expression<Func<Encoding>> encoding)
        {
            bool isUniversal = true;

            Type decoratedTypeWithHelper = helper.IsGenericTypeDefinition ? helper.MakeGenericType(type) : helper;
            Type realType = Serializer.UndecorateType(type);

            if (realType != typeof(string))
            {
                throw new NotSupportedException();
            }

            Type subType = typeof(byte[]);

            Descriptor descriptor1 = Serializer.GetSerializer(serializer, subType, subType, helper);
            isUniversal &= descriptor1.IsUniversal;

            Type encodingType = encoding.Body.Type;

            MethodInfo getByteCount = encodingType.GetMethod("GetByteCount", BindingFlags.Instance | BindingFlags.Public, null, new[] { realType }, null);
            MethodInfo getBytes = encodingType.GetMethod("GetBytes", BindingFlags.Instance | BindingFlags.Public, null, new[] { realType }, null);
            MethodInfo getString = encodingType.GetMethod("GetString", BindingFlags.Instance | BindingFlags.Public, null, new[] { subType }, null);

            PropertyInfo length = realType.GetProperty("Length", BindingFlags.Instance | BindingFlags.Public, null, typeof(int), Type.EmptyTypes, null);

            ParameterExpression item = Expression.Parameter(realType);
            ParameterExpression parameters = parametersType != null ? Expression.Parameter(parametersType) : null;
            ParameterExpression subItem = Expression.Variable(subType);

            ConstructorInfo constructor = decoratedTypeWithHelper.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
            var helper2 = (ICollectionHelper)constructor.Invoke(null);
            isUniversal &= helper2.IsUniversal;

            LambdaExpression deserializeExpression;
            LambdaExpression serializeExpression;
            LambdaExpression sizeExpression;

            /***/
            {
                var block = new List<Expression>();

                if (!(descriptor1.DefaultExpression is DefaultExpression))
                {
                    block.Add(Expression.Assign(subItem, descriptor1.DefaultExpression));
                }

                block.Add(Serializer.MakeFromLambda(descriptor1.DeserializeExpression, descriptor1.IsUniversal, Serializer.ReaderExpression, subItem, parameters));

                BlockExpression expression = Expression.Block(new[] { subItem }, block);

                deserializeExpression = Serializer.MakeLambda(Expression.Call(encoding.Body, getString, expression), descriptor1.IsUniversal, Serializer.ReaderExpression, item, parameters);
            }

            /***/
            {
                BlockExpression expression = Expression.Block(
                    new[] { subItem },
                    new Expression[] 
                    {
                        Expression.Assign(subItem, Expression.Call(encoding.Body, getBytes, item)),
                        Serializer.MakeFromLambda(descriptor1.SerializeExpression, descriptor1.IsUniversal, Serializer.WriterExpression, subItem, parameters)
                    });

                serializeExpression = Serializer.MakeLambda(expression, descriptor1.IsUniversal, Serializer.WriterExpression, item, parameters);
            }

            /***/
            {
                MethodCallExpression length2 = Expression.Call(encoding.Body, getByteCount, item);
                Expression size = length2;

                ParameterExpression size2 = Expression.Variable(typeof(int));
                Expression baseSize = helper2.SizeLength(size2);

                if (baseSize != null)
                {
                    size = Expression.Block(
                        new[] { size2 },
                        new Expression[] 
                        {
                            Expression.Assign(size2, length2),
                            Expression.Add(baseSize, size2)
                        });
                }

                sizeExpression = Serializer.MakeLambda(size, descriptor1.IsUniversal, item, parameters);
            }

            var descriptor2 = new Descriptor(decoratedTypeWithHelper, isUniversal, deserializeExpression, DeserializationHandling.RequireAssignment, serializeExpression, sizeExpression);
            return descriptor2;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serializer"></param>
        /// <param name="parametersType"></param>
        /// <param name="type"></param>
        /// <param name="helper"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "More readable this way")]
        public static Descriptor WithCharLengthBuildSerializer(Serializer serializer, Type parametersType, Type type, Type helper, Expression<Func<Encoding>> encoding)
        {
            var encoding2 = encoding.Compile()();

            if (!encoding2.IsFixedLength())
            {
                throw new NotSupportedException("Only fixed length encodings can be serialized");
            }

            var factor = encoding2.MaxCharLength();

            Type decoratedTypeWithHelper = helper.IsGenericTypeDefinition ? helper.MakeGenericType(type) : helper;
            Type realType = Serializer.UndecorateType(type);

            Type variableLengthHelper = typeof(VariableLengthChanger<,>).MakeGenericType(helper.MakeGenericType(type), typeof(byte).MakeArrayType(factor));
            Descriptor descriptor1 = WithByteLengthBuildSerializer(serializer, parametersType, type, variableLengthHelper, encoding);

            PropertyInfo length = realType.GetProperty("Length", BindingFlags.Instance | BindingFlags.Public, null, typeof(int), Type.EmptyTypes, null);

            ParameterExpression item = Expression.Parameter(realType);
            ParameterExpression parameters = parametersType != null ? Expression.Parameter(parametersType) : null;

            ConstructorInfo constructor = decoratedTypeWithHelper.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
            var helper2 = (ICollectionHelper)constructor.Invoke(null);

            LambdaExpression sizeExpression;

            /***/
            {
                MemberExpression length2 = Expression.Property(item, length);
                Expression baseSize = helper2.SizeLength(length2);

                Expression size = length2;

                if (baseSize != null)
                {
                    size = Expression.Add(baseSize, size);
                }

                sizeExpression = Serializer.MakeLambda(size, descriptor1.IsUniversal, item, parameters);
            }

            var descriptor2 = new Descriptor(variableLengthHelper, descriptor1.IsUniversal, descriptor1.DeserializeExpression, descriptor1.DeserializationHandling, descriptor1.SerializeExpression, sizeExpression, descriptor1.DisposeExpression);
            return descriptor2;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TRank">The rank of the array is the factor. The maximum rank is 32.</typeparam>
        public class VariableLengthChanger<T, TRank> : ICollectionHelper where T : ICollectionHelper, new()
        {
            /// <summary>
            /// 
            /// </summary>
            public readonly T Helper;

            /// <summary>
            /// 
            /// </summary>
            public readonly ConstantExpression Factor;

            /// <summary>
            /// 
            /// </summary>
            public VariableLengthChanger()
            {
                this.Helper = new T();
                this.Factor = Expression.Constant(typeof(TRank).GetArrayRank());
            }

            #region ICollectionHelper Members

            /// <summary>
            /// 
            /// </summary>
            public bool IsUniversal
            {
                get { return this.Helper.IsUniversal; }
            }

            /// <summary>
            /// 
            /// </summary>
            public bool IsFixedLength
            {
                get { return this.Helper.IsFixedLength; }
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="reader"></param>
            /// <param name="length"></param>
            /// <returns></returns>
            public Expression DeserializeLength(Expression reader, Expression length)
            {
                var expression = this.Helper.DeserializeLength(reader, length);

                if (expression != null)
                {
                    expression = Expression.Multiply(expression, this.Factor);
                }

                return expression;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="writer"></param>
            /// <param name="length"></param>
            /// <returns></returns>
            public Expression SerializeLength(Expression writer, Expression length)
            {
                var expression = this.Helper.SerializeLength(writer, Expression.Divide(length, this.Factor));
                return expression;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="length"></param>
            /// <returns></returns>
            public Expression SizeLength(Expression length)
            {
                return this.Helper.SizeLength(length);
            }

            #endregion
        }
    }
}